#pragma once

#include "limbo/chunk.hpp"
#include "limbo/errc.h"
#include "limbo/result.h"

namespace limbo::slip {

static constexpr unsigned char END = 0xC0;
static constexpr unsigned char ESC = 0xDB;
static constexpr unsigned char ESC_END = 0xDC;
static constexpr unsigned char ESC_ESC = 0xDD;

template <typename State> Result escape(Chunk buff, Chunk data, State *state) {
  auto buff_ptr = static_cast<unsigned char *>(buff.data());
  auto data_ptr = static_cast<unsigned char *>(data.data());
  auto i = data_ptr;
  auto end = i + data.size();

  uint32_t count = 1; /* end */

  while (i != end) {
    bool escape = (*i == END) || (*i == ESC);
    if (escape) {
      ++count;
    }
    ++i;
  }
  if (buff.size() < data.size() + count) {
    auto code = (uint32_t)Errc::no_enough_space;
    return Result::make_error(code, state, data);
  }

  auto o = buff_ptr + data.size() + count - 1;
  *o-- = END;
  --i;
  while (i >= data_ptr) {
    auto c = *i;
    if (c == END) {
      *o-- = ESC_END;
      *o-- = ESC;
    } else if (c == ESC) {
      *o-- = ESC_ESC;
      *o-- = ESC;
    } else {
      *o-- = c;
    }
    --i;
  }

  return Result::make_consumed(state, Chunk(buff_ptr, data.size() + count));
}

template <typename State> Result unescape(Chunk buff, State *state) {
  auto start = static_cast<unsigned char *>(buff.data());
  auto i = start + state->position;
  auto o = i;
  auto end = start + buff.size();
  bool eos = false;
  while (i != end) {
    if (*i == END) {
      eos = true;
      break;
    }
    if (*i == ESC) {
      if (i + 1 < end) {
        auto c = *(i + 1);
        if (c == ESC_END) {
          *o++ = END;
        } else if (c == ESC_ESC) {
          *o++ = ESC;
        } else {
          // it is actually broken stream, handled
          // by upper layer
          *o++ = c;
        }
        i += 2;
        if (state->offset) {
          --state->offset;
        }
      } else {
        ++state->offset;
        break;
      }
    } else {
      *o++ = *i++;
    }
  }
  auto bytes = i - start;
  if (eos) {
    auto consumed = Chunk(start, bytes + 1);
    auto payload = Chunk(start, o - start);
    state->packet.payload = payload;
    state->position = 0;
    state->offset = 0;
    return Result::make_consumed(state, consumed);
  } else {
    auto consumed = Chunk(start, bytes);
    state->position = o - start;
    auto code = (uint32_t)Errc::slip_incomplete;
    return Result::make_error(code, state, consumed);
  }
}

} // namespace limbo::slip
